This is to be completed in Python. You are given a dataset that contains the geo-codes for where customers shop and where customers live. You need to create (as best as you can) an interactive map that accomplishes the following: when a user clicks a shop, the customers who shop there are highlighted, lines are drawn from all the customers to that shop, or some such visual that helps the user to visualize where a store’s customers come from.
install plotly --upgrade to make sure you're using the latest version.(!!! Be sure to change your path accordingly if data set is not in the same folder with the notebook.)
import pandas as pd
import numpy as np
# read file (!!! be sure to change your path accordingly)
df = pd.read_excel('LaptopSalesLL.xlsx')
df.head()
# found abnormal values after exploration
# normalize dataset, replace them with NaN
# change to numeric values
df['Customer Latitude'] = df['Customer Latitude'].replace('FAILED','')
df['Customer Longitude'] = df['Customer Longitude'].replace('FAILED','')
df['Customer Latitude'] = pd.to_numeric(df['Customer Latitude'])
df['Customer Longitude'] = pd.to_numeric(df['Customer Longitude'])
# check for nulls
df.isnull().sum()
# remove nulls
df = df[df["Customer Latitude"].notnull()]
df = df[df["Customer Longitude"].notnull()]
df = df[df["Store Latitude"].notnull()]
df = df[df["Store Longitude"].notnull()]
df.isnull().sum()
# make new data frame
new_df = df
# reset index
new_df = new_df.reset_index(drop=True)
# separate useful data from dataframe
Customer_Latitude = new_df['Customer Latitude']
Customer_Longitude = new_df['Customer Longitude']
Store_Latitude = new_df['Store Latitude']
Store_Longitude = new_df['Store Longitude']
# make customer and store coordinate pairs for finding unique points
new_df['C_Coord'] = list(zip(new_df['Customer Longitude'], new_df['Customer Latitude']))
new_df['S_Coord'] = list(zip(new_df['Store Longitude'], new_df['Store Latitude']))
# number of customers
customers = new_df['C_Coord'].unique()
len(customers)
# number of stores
stores = new_df['S_Coord'].unique()
len(stores)
len(df)
# prepare data for making graph objects
stores_lon = [i[0] for i in stores]
stores_lat = [i[1] for i in stores]
customers_lon = [i[0] for i in customers]
customers_lat = [i[1] for i in customers]
import plotly
import plotly.plotly as py
import plotly.graph_objs as go
plotly.offline.init_notebook_mode()
# make store object points
cases_s = []
cases_s.append(go.Scattermapbox(
lon = stores_lon,
lat = stores_lat,
marker = {'color': 'rgb(51,204,153)', 'size': 15}, # 'symbol': 'star', if you don't need marker.color function.
### Note that the array `marker.color` and `marker.size` are only available for "circle" symbols.
name = 'stores',
mode = 'markers',
text = []
))
# check if object is successfully built
cases_s[0]
# make customer object points
cases_c = []
cases_c.append(go.Scattermapbox(
lon = customers_lon,
lat = customers_lat,
marker = {'color': 'rgb(255,153,0)', 'size': 8},
name = 'customers',
mode = 'markers',
opacity = .5,
text = []
))
# check if object is successfully built
cases_c[0]
# find unique links between all stores and customers
new_df['relations'] = list(zip(new_df['C_Coord'], new_df['S_Coord']))
relations = new_df['relations'].unique()
len(relations)
# check relations structure
relations[:][0]
# (c_lon,c_lat),(s_lon,s_lat)
# build links for each store and store them in a list
all_links = []
number_of_links = []
for store in range(len(stores)):
select = [i for i in relations if i[1] == stores[store]] # find each store
select_lon_left = [i[0][0] for i in select] # prepare longitude of each point for links
select_lon_right = [i[1][0] for i in select]
select_lon = []
for i in range(len(select)):
select_lon.append(select_lon_left[i])
select_lon.append(select_lon_right[i])
select_lat_left = [i[0][1] for i in select] # prepare latitude of each point for links
select_lat_right = [i[1][1] for i in select]
select_lat = []
for i in range(len(select)):
select_lat.append(select_lat_left[i])
select_lat.append(select_lat_right[i])
number_of_links.append(len(select_lon_left))
links = [] # build links for each store
links.append(go.Scattermapbox(
lon = select_lon,
lat = select_lat,
mode = 'lines',
line = {'width': 2, 'color': 'rgb(51,204,153)'},
name = 'links_to_store_' + str(store + 1),
text = 'There are ' + str(len(select_lon)//2) + ' links to store_' + str(store + 1) ## pop-up for links
))
all_links.append(links) # combine all link sets
# check if link object is successfully built
all_links[0]
# build links for each customer and store them in a list
all_links_cust = []
number_of_links_cust = []
for customer in range(len(customers)):
select = [i for i in relations if i[0] == customers[customer]] # find each store
select_lon_left = [i[0][0] for i in select] # prepare longitude of each point for links
select_lon_right = [i[1][0] for i in select]
select_lon = []
for i in range(len(select)):
select_lon.append(select_lon_left[i])
select_lon.append(select_lon_right[i])
select_lat_left = [i[0][1] for i in select] # prepare latitude of each point for links
select_lat_right = [i[1][1] for i in select]
select_lat = []
for i in range(len(select)):
select_lat.append(select_lat_left[i])
select_lat.append(select_lat_right[i])
number_of_links_cust.append(len(select_lon_left))
links = [] # build links for each customer
links.append(go.Scattermapbox(
lon = select_lon,
lat = select_lat,
mode = 'lines',
line = {'width': 2, 'color': 'rgb(255,51,102)'},
name = 'links_to_customer_' + str(customer + 1),
text = 'There are ' + str(len(select_lon)//2) + ' links to customer_' + str(customer + 1) ## pop-up for links
))
all_links_cust.append(links) # combine all link sets
all_links_cust[0]
# add text to the popup
text = []
for i in number_of_links:
text.append(str(i) + ' customers visited here!!!')
cases_s[0].text = text
cases_s[0]
# add text to the popup
text_cust = []
for i in number_of_links_cust:
text_cust.append('This customer visited ' + str(i) + ' store(s)!!!')
cases_c[0].text = text_cust
cases_c[0]
These are projections of map you can choose from if using scattergeo instead of scattermapbox:
'equirectangular', 'mercator', 'orthographic', 'natural earth', 'kavrayskiy7', 'miller', 'robinson', 'eckert4', 'azimuthal equal area', 'azimuthal equidistant', 'conic equal area', 'conic conformal', 'conic equidistant', 'gnomonic', 'stereographic', 'mollweide', 'hammer', 'transverse mercator', 'albers usa', 'winkel tripel', 'aitoff', 'sinusoidal'
mapbox_access_token = 'pk.eyJ1IjoiYWRhbGl4IiwiYSI6ImNqcGRjbDJqMDBmbDUzcG8yank1cjY1dmcifQ.RfN9GixVVUltAg3z4ezmKw'
!!! This the mapbox API access token. You can also create your own at https://www.mapbox.com/studio/
# prepare layout for mapping
layout = go.Layout(
autosize = True,
hovermode = 'closest',
title = 'Map: Customer & Store Relationship',
mapbox = dict(
accesstoken = mapbox_access_token,
bearing = 0,
center = dict(
lat = 51,
lon = 0
),
pitch = 0,
zoom = 5
),
)
######### This is the version without Scattermapbox #####################
#### Change all Scattermapbox object to Scattergeo if you want to use this version #####
# layout = go.Layout(
# title = 'Map: Customer & Store Relationship',
# geo = dict(
# resolution = 110,
# scope = 'world',
# showframe = False,
# showcoastlines = False,
# showland = True,
# landcolor = "rgb(229, 229, 229)" ,
# countrycolor = "rgb(255, 255, 255)",
# coastlinecolor = "rgb(255, 255, 255)",
# projection = dict( type = 'equirectangular' ), # choose as your preference from above
# lonaxis = dict( range = [ -1.0, 1.0 ] ),
# lataxis = dict( range = [ 51.0, 52.0 ] ),
# ),
# # legend = dict(traceorder = 'reversed'), # choose as your preference
# )
This is the part I need to use array like marker.color.
For this part to work properly, the only option for marker shape is circle.
Documentation : "Sets the marker symbol. Full list: https://www.mapbox.com/maki-icons/ Note that the array marker.color and marker.size are only available for "circle" symbols.".
# prepare link object structure to pass in the figure
temp = [go.Scattermapbox({
'line': {'color': 'rgb(51,204,153)', 'width': 2},
'mode': 'lines',
})]
temp_cust = [go.Scattermapbox({
'line': {'color': 'rgb(255,51,102)', 'width': 2},
'mode': 'lines',
})]
# make map figure
f = go.FigureWidget(layout = layout, data = temp + cases_c + temp_cust + cases_s)
# prepare stores object for interactive actions
scatter_cust = f.data[1]
scatter_cust.marker.color = ['rgb(255,153,0)'] * len(customers)
scatter = f.data[3]
scatter.marker.color = ['rgb(51,204,153)'] * 15
scatter.marker.size = [15] * 15
f.layout.hovermode = 'closest'
# prepare links object for interactive actions
lines = f.data[0]
lines_cust = f.data[2]
# callback function for click_on interaction
# you can see the color and size of the store updated on the map!!
def update_point(trace, points, selector):
c = list(scatter.marker.color)
s = list(scatter.marker.size)
for i in points.point_inds:
# update clicked store and links
c[i] = 'rgb(255,51,102)'
s[i] = 25
scatter.marker.color = c
scatter.marker.size = s
# get index of clicked store and pass it to links for update
lines.lat = all_links[i][0].lat
lines.lon = all_links[i][0].lon
lines.name = all_links[i][0].name
lines.text = all_links[i][0].text
def update_point_cust(trace, points, selector):
c = list(scatter_cust.marker.color)
for i in points.point_inds:
# update clicked customer and links
c[i] = 'blue'
scatter_cust.marker.color = c
# get index of clicked customer and pass it to links for update
lines_cust.lat = all_links_cust[i][0].lat
lines_cust.lon = all_links_cust[i][0].lon
lines_cust.name = all_links_cust[i][0].name
lines_cust.text = all_links_cust[i][0].text
# click interactive function
scatter.on_click(update_point)
scatter_cust.on_click(update_point_cust)
Green Circle : unclicked storesRed Circle : clicked storesYellow Dot : unclicked custmersBlue Dot : clicked customersGreen Lines : store -----> customersRed Lines : customer -----> storesPop-up Window contains: customers came to a certain store; stores a certain customer visited; lines draw from which store; lines draw from which customer;Pop-up Window if it's the object you want to select before clicking. Hide other objects if you wish to click more accurately.store is clicked, it will change from Green Circle to bigger Red Circle. Green Lines will draw from store.custmer is clicked, it will change from Yellow Circle to Blue Circle. Red Lines will draw from custmer.plotly.tools.set_credentials_file(username = 'adalix', api_key = 'Xy22gptCqzr6eQYewPDD')
!!!! This is my Plotly API access, please don't share this publicly. You can also create your own at https://plot.ly/feed/#/
# show figure
## make sure you run all the aboves for widgets to show.
f
# show figure
## make sure you run all the aboves for widgets to show.
f_alt = go.FigureWidget(layout = layout, data = \
all_links[0] + all_links[1] + all_links[2] + all_links[3] + \
all_links[4] + all_links[5] + all_links[6] + all_links[7] + \
all_links[8] + all_links[9] + all_links[10] + all_links[11] + \
all_links[12] + all_links[13] + all_links[14] + cases_c + cases_s)
f_alt
Show Map with Click_on Interaction part.from PIL import Image
Image.open("click_on.jpeg")
Image.open("legend_1.png")